# -*- coding: utf-8 -*-


import os, glob, shutil
import simplejson
import logging
import time
import zipfile

from threading import Timer
from twisted.internet import protocol, stdio, defer
from twisted.protocols import basic
from twisted.internet.protocol import ClientFactory

from utils.common import COMMANDS, random_filename, validate_file_md5_hash, get_file_md5_hash, read_bytes_from_file

import clutter


ROOT_DIR = os.path.dirname(__file__)
ZIP_DIR = os.path.join(ROOT_DIR, 'downloadedfiles')
WIDGETS_DIR_REL = 'unzipwidgets'
WIDGETS_DIR_ABS = os.path.join(ROOT_DIR, WIDGETS_DIR_REL)
DOWNLOADED_FILES_FOLDER = os.path.join(ROOT_DIR, 'downloadedfiles')


log = logging.getLogger("AlicanteClutter")

def unzipWidget(zip):
    isdir = os.path.isdir
    join = os.path.join
    norm = os.path.normpath
    split = os.path.split

    configFile = False

    zip_folder = join( WIDGETS_DIR_REL, split(os.path.splitext(zip.filename)[0])[-1] )
    os.makedirs(zip_folder)

    for each in zip.namelist():
        if not each.endswith('/'):
            root, name = split(each)
            directory = norm(join(zip_folder, root))
            if not isdir(directory):
                os.makedirs(directory)
            file(join(zip_folder, name), 'wb').write(zip.read(each))
            
            if name == "conf.js":
                configFile = join(zip_folder, name)
    
    if not configFile:
        raise IOError("Can't find config file in %s" % zip.filename)
    
    return zip_folder



class Event:
    def __init__(self, conf, widgetPath):
        self.start = conf["startTime"]
        self.end = conf["stopTime"]
        self.eventType = conf["serviceType"]
        self.channel = int(conf["channelRef"])
        self.started = False
        self.widgetPath = os.path.join(widgetPath, conf["widgetFile"])
        
        self.start = time.time() - 10
        self.end = time.time() + 10
        
        
    def getWidgetPath(self):
        return self.widgetPath
    
    
    
class EventsProtocol(basic.LineReceiver):
    delimiter = '\n'
    def __init__(self):
        self.listEvents = {}
        self.missingWidgets = []
        self.homebox_available_widgets = []
        self.nextUpdate = None
        self.timer = None
        
        self.twistedThread = None
        self.cleanUnzipFolder()
        self.widgets = None
        
        
    def cleanUnzipFolder(self):
        for root, dirs, files in os.walk(WIDGETS_DIR_ABS):
            for f in files:
                if f != "__init__.py":
                    os.unlink(os.path.join(root, f))
            for d in dirs:
                shutil.rmtree(os.path.join(root, d),  ignore_errors=True)
                
    
    def displayAvailableWidgets(self):
        now = time.time()
        nextUpdateTemp = False
        
        for path, event in self.listEvents.iteritems():
            if event.start <= now <= event.end:
                self.factory.widgetsManager.showWidget( event.getWidgetPath() )
                event.started = True
                if not nextUpdateTemp or (event.end <= nextUpdateTemp):
                    nextUpdateTemp = event.end
                    
            elif event.started == True:
                self.factory.widgetsManager.removeWidget( event.getWidgetPath() )
                
        #if nextUpdateTemp != self.nextUpdate:
            #self.nextUpdate = nextUpdateTemp
            #if self.timer != None:
                #self.timer.cancel()
            #self.timer = Timer( self.nextUpdate - now, self.updateEvents )
            #self.timer.start()
        self.factory.widgetsManager.update_available_widgets()
            
            
    def connectionMade(self):
        self.buffer = []
        self.file_handler = None
        self.file_data = ()
        log.debug('Connected to the server')
        commandtemp = {'action' : 'setchannel', 'value' : '1'}
        self.transport.write( '%s\n' % simplejson.dumps(commandtemp) )
        
    def connectionLost(self, reason):
        self.file_handler = None
        self.file_data = ()
        log.debug('Connection to the server has been lost')
        
    
    def getMissingWidgets(self):
        if self.widgets == None:
            self.widgets = self.scanArchivesWidgets()
        if not self.missingWidgets:
            self.missingWidgets = set(self.homebox_available_widgets) - set(self.widgets)

        if self.missingWidgets:
            log.debug("Missing %s" % self.missingWidgets)
            # We go one by one
            commandtemp = {'action' : 'getfile', 'value' : self.missingWidgets.pop()}
            self.transport.write( '%s\n' % simplejson.dumps(commandtemp) )
        else:
            log.debug("No missing widget, show the widgets")
            self.updateEvents()
    
    def scanArchivesWidgets(self):
        self.widgets = []
        for filename in os.listdir(ZIP_DIR):
            file_path = os.path.join(ZIP_DIR, filename)
            
            if os.path.isdir(file_path):
                continue
            
            file_size = os.path.getsize(file_path)
            md5_hash = get_file_md5_hash(file_path)

            self.widgets.append( (md5_hash, file_size) )

        return self.widgets
    
    def lineReceived(self, line):
        try:
            data = simplejson.loads(line)
        except ValueError, e:
            log.error("can't decode: %s" % e)
        else:
            command = data['action'].upper()
            
            if command == 'GETCHANNEL':
                log.debug("Server wants to know current channel")
                commandtemp = {'action' : 'setchannel', 'value' : '1'}
                self.transport.write( '%s\n' % simplejson.dumps(commandtemp) )
                
            elif command == 'LISTWIDGETS':
                log.debug("Received the available widgets for this channels")
                
                try:
                    for widget_infos in data['value']:
                        self.homebox_available_widgets.append( (widget_infos[0], widget_infos[1]) )
                except ValueError, e:
                    log.error("can't decode: %s" % e)
                else:
                    self.getMissingWidgets()
                
            elif command == 'FILETRANSFERT':
                try:
                    file_hash = data['hash']
                    file_size = data['filesize']
                except ValueError, e:
                    log.error("Can't decode: %s" % e)
                else:
                    rand_filename = random_filename(suffix=".zip", directory=DOWNLOADED_FILES_FOLDER)
                    if rand_filename:
                        self.file_data = (rand_filename,
                            file_hash,
                            file_size)
                        self.setRawMode()
                    else:
                        log.error("Can't find random filename... Weird")
                
            elif command == 'ERRORMSG':
                try:
                    log.error("Error from server %s" % data['value'])
                except:
                    pass
                
            else:
                log.error("Unknown command from server: %s" % command) 
        
    def rawDataReceived(self, data):
        filename = self.file_data[0]
        file_path = os.path.join(DOWNLOADED_FILES_FOLDER, filename)
            
        log.debug( 'Receiving file chunk (%d KB)' % (len(data)) )
        
        if not self.file_handler:
            self.file_handler = open(file_path, 'wb')
            
        if data.endswith('\r\n'):
            # Last chunk
            data = data[:-2]
            self.file_handler.write(data)
            self.setLineMode()
            
            self.file_handler.close()
            self.file_handler = None
            
            if validate_file_md5_hash(file_path, self.file_data[1]):
                log.debug( 'File %s has been successfully transfered and saved' % (filename) )
            else:
                os.unlink(file_path)
                log.debug( 'File %s has been successfully transfered, but deleted due to invalid MD5 hash' % (filename) )
            try:
                self.widgets.append( (self.file_data[1], self.file_data[2]) )
            except AttributeError:
                log.error("self.widgets was not initialize")
            self.getMissingWidgets()
        else:
            self.file_handler.write(data)
    
    def updateEvents(self):
        """
        Parse zip folder, extract new widgets and add them to the widgets manager
        When a widget expires, we remove the zip and the unzip folder
        """
        for infile in glob.glob( os.path.join(ZIP_DIR, '*.zip') ):
            if not self.listEvents.has_key(infile):
                try:
                    zip = zipfile.ZipFile(infile, 'r')
                except IOError, e:
                    log.error(e)
                else:
                    try:
                        widgetpath = unzipWidget(zip)
                    except IOError, e:
                        log.error(e)
                    else:
                        try:
                            confObject = simplejson.load(open(os.path.join(widgetpath, 'conf.js'), 'r'))
                        except ValueError, e:
                            log.error("Error loading conf file: %s. %s" % infile, e)
                        else:
                            try:
                                event = Event(confObject, widgetpath)
                            except KeyError, e:
                                log.error("Can't find %s in config file" % e)
                            else:
                                try:
                                    self.factory.widgetsManager.load( event.getWidgetPath() )
                                except ImportError, e:
                                    log.error(e)
                                else:
                                    self.listEvents[infile] = event
                                
                    zip.close()
        
        self.displayAvailableWidgets()



# When we receive a zip file, we check if we already have a file with a similar name, if so and if the two archives are different, we change the file name



class ClientHomeboxFactory(protocol.ReconnectingClientFactory):
    protocol = EventsProtocol
    
    def __init__(self, widgetsManager):
        self.widgetsManager = widgetsManager
        clutter.init()
        clutter.threads_init()
        #self.deferred = defer.Deferred()
        
    def connectionMade(self):
        self.resetDelay()
        
    def connectionLost(self, connector, reason):
        protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
        log.debug( 'Connection to the server has been lost (%s)' % reason )
        
    def clientConnectionFailed(self, connector, reason):
        log.debug( 'Connection failed (%s)' % reason )
        protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)   

